%%% Copyright 2010 Google Inc.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%%     http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.

%%% @copyright 2010 Google Inc. All Rights Reserved.
%%% @author <jacoby@google.com> Jacoby Thwaites [http://sites/madcloud]
%%% @version {@version}
%%% @doc
%%% Supervisor for view processes started by clients.
%%% See [http://www.erlang.org/doc/man/supervisor.html]
%%% @headerfile "../include/view.hrl"
%%%
%%% @todo(jacoby) Determine the restart policy (presently 5 restarts allowed
%%% in 60 seconds before this supervisor is restarted).

-module(view_sup).

-behaviour(supervisor).

-include("txm.hrl").
-include("view.hrl").

-compile(export_all).

%% Client functions.
-export([start_link/0]).

%% Supervisor callback function.
-export([init/1]).

%% Unused function compiler warning suppression.
-export([periodic_clean/2]).

%% Starts the registered view supervisor process and links to parent.
start_link() ->
  supervisor:start_link({local, ?MODULE}, ?MODULE, []).

%% Returns a simple one-for-one supervisor suitable for all dynamically
%% started view_fsm processes.
init(_Arg) ->
  ?INFO([init]),
  process_flag(trap_exit, true),

  %% Cleans out inactive view_fsm children periodically
  start_cleaner(self(), ?VIEW_CLEANER_INTERVAL_MILLIS, ?VIEW_MAX_IDLE_MILLIS),

  ?OK_RETURN({{simple_one_for_one, 0, 1}, [view_fsm_spec()]}).

%% Returns the template simple-one-for-one child specification for
%% view_fsm processes. Each view_fsm is transient (ie not restarted if
%% terminates normally) and is given 5 seconds to shutdown when asked.
view_fsm_spec() ->
  {view_fsm,
   {view_fsm, start_link, []},
   temporary, % Never restarted
   ?SHUTDOWN_TIME,
   worker,
   [view_fsm]}.

%% Starts the cleaner which checks and removes inactive view_fsm processes.
%% Returns the opaque timer reference that can be used to cancel the timer.
%% @spec start_cleaner(pid(), integer(), integer()) -> {ok, term()}
start_cleaner(SupRef, IntervalMillis, MaxIdleMillis) ->
  ?INFO([start_view_cleaner,
         {interval, ?VIEW_CLEANER_INTERVAL_MILLIS},
         {maxIdle, ?VIEW_MAX_IDLE_MILLIS}]),
  timer:apply_interval(IntervalMillis,
                       ?MODULE, periodic_clean, [SupRef, MaxIdleMillis]).

%% @doc Called periodically by the timer:apply_interval/4. Shuts down inactive
%% client_fsm children of this supervisor, returns the number of children
%% shutdown.
%% @spec periodic_clean(pid(), integer()) -> integer()
periodic_clean(SupRef, MaxIdleMillis) ->
  ?DEBUG([periodic_view_clean]),
  InactiveChildren = [Child || {_Id, Child, _Type, _Modules} <-
              supervisor:which_children(SupRef),
                               inactive_longer_than(Child, MaxIdleMillis)],
  lists:foldl(
    fun(ViewFsm, Acc) ->
      ?WARNING([stop, {inactive, ViewFsm}]),
      view_fsm:stop(ViewFsm),
      Acc + 1
    end,
    0, InactiveChildren).

%% @doc Pings the specified view_fsm process for its last used time, and
%% returns true if this is longer ago than the supplied `Millis'.
%% @spec inactive_longer_than(pid(), integer()) -> boolean()
inactive_longer_than(ViewFsm, Millis) ->
  ViewFsm ! {self(), last_used},
  Time = receive
    {ViewFsm, LastUsed} -> LastUsed
  end,

  % Note the timer:now_diff/2 gives microseconds difference.
  timer:now_diff(now(), Time)/1000 > Millis.
